package bgw

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"time"

	"gitee.com/liuxuezhan/ar-platform/base-common/uuid"
	"github.com/spf13/cast"
)

const (
	withdrawPath       = `/api/withdraw/create`
	withdrawDetailPath = `/api/withdraw/detail`
)

type WithdrawService interface {
	Withdraw(param WithdrawRequest) (resp WithdrawResponseData, err error) // 提现申请
	WithdrawCallbackVerify(_header http.Header, body []byte) (resp WithdrawCallbackRequest, err error)
	SetUrl(url string) WithdrawService
	SetNotifyUrl(url string) WithdrawService
	GetCurrencyId(code string) int64
	WithdrawDetail(orderNo string) (resp WithdrawCallbackRequest, err error) // 提现订单查询
}

type withdraw struct {
	MerchantId string
	AppId      string
	AppCode    string
	AppSecret  string
	Url        string
	NotifyUrl  string
}

func NewWithdrawService(merchantId, appId, appCode, appSecret string) WithdrawService {
	return &withdraw{
		MerchantId: merchantId,
		AppId:      appId,
		AppCode:    appCode,
		AppSecret:  appSecret,
	}
}

func (w *withdraw) Withdraw(param WithdrawRequest) (resp WithdrawResponseData, err error) {
	if param.NotifyURL == "" {
		param.NotifyURL = w.NotifyUrl
	}
	// 根据scale计算amount
	param.calAmount()
	marshal, err := json.Marshal(param)
	if err != nil {
		return
	}
	requestId := uuid.GenUUID().String()
	nonce := uuid.GenUUID().String()
	created := time.Now().Format(time.RFC3339)

	passwordDigest := GeneratePasswordDigest(nonce, created, w.AppSecret)

	sign := GenerateBodySign(string(marshal), w.AppCode, w.AppSecret, requestId)

	req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", w.Url, withdrawPath), bytes.NewReader(marshal))
	if err != nil {
		return
	}
	// Set the appropriate headers
	req.Header.Set("Content-Type", "application/json; charset=UTF-8")
	req.Header.Set("Merchant-Id", w.MerchantId)
	req.Header.Set("Request-Id", requestId)
	req.Header.Set("Authorization", formatAuthorization(w.AppId))
	req.Header.Set("X-WSSE", formatXWSSE(w.AppCode, passwordDigest, nonce, created))
	req.Header.Set("Body-Sign", formatBodySing("HMAC-SHA256", sign))

	// Create a new HTTP client and send the request
	client := &http.Client{}
	response, err := client.Do(req)
	if err != nil {
		return
	}
	defer response.Body.Close()

	// Read the response body
	bodyResp, err := io.ReadAll(response.Body)
	if err != nil {
		return
	}
	_response := Response{}
	err = json.Unmarshal(bodyResp, &_response)
	if err != nil {
		return
	}
	if _response.Code != 0 {
		err = errors.New(_response.Msg)
		return
	}
	respMap := _response.Data.(map[string]interface{})
	resp = WithdrawResponseData{
		OrderNo:      cast.ToString(respMap["orderNo"]),
		OuterNo:      cast.ToString(respMap["outerNo"]),
		AppId:        cast.ToInt64(respMap["appId"]),
		CurrencyId:   cast.ToInt64(respMap["currencyId"]),
		Amount:       cast.ToInt64(respMap["amount"]),
		Scale:        cast.ToInt64(respMap["scale"]),
		Type:         cast.ToInt64(respMap["type"]),
		CurrencyName: cast.ToString(respMap["currencyName"]),
		CreateTime:   cast.ToInt64(respMap["createTime"]),
	}
	// 转化提现金额
	resp.calAmount()
	return
}

// 提现回调验证

func (w *withdraw) WithdrawCallbackVerify(_header http.Header, body []byte) (resp WithdrawCallbackRequest, err error) {
	// merchantId := _header.Get("Merchant-Id")
	requestId := _header.Get("Request-Id")
	authorization := _header.Get("Authorization")
	result, err := ParseAuthorization(authorization)
	if err != nil {
		return
	}
	appId := result["realm"]
	if w.AppId != appId {
		err = errors.New("appid is not matched")
		return
	}
	xWSSE := _header.Get("X-WSSE")
	xwsse, err := ParseXWSSE(xWSSE)
	if err != nil {
		return
	}
	appCode := xwsse["App"]
	if appCode != w.AppCode {
		err = errors.New("appcode is not matched")
		return
	}
	passwordDigest := xwsse["PasswordDigest"]
	nonce := xwsse["Nonce"]
	created := xwsse["Created"]

	digest := GeneratePasswordDigest(nonce, created, w.AppSecret)
	if passwordDigest != digest {
		err = errors.New("password digest verify fail")
		return
	}
	bodySign := _header.Get("Body-Sign")
	signature, err := ParseBodySign(bodySign)
	if err != nil {
		return
	}
	sign := GenerateBodySign(string(body), appCode, w.AppSecret, requestId)
	if signature["signature"] != sign {
		err = errors.New("signature verify fail")
		return
	}
	resp = WithdrawCallbackRequest{}
	err = json.Unmarshal(body, &resp)
	if err != nil {
		return
	}
	resp.calAmount()
	return
}

func (w *withdraw) SetUrl(url string) WithdrawService {
	w.Url = url
	return w
}

func (w *withdraw) SetNotifyUrl(url string) WithdrawService {
	w.NotifyUrl = url
	return w
}

func (w *withdraw) GetCurrencyId(code string) int64 {
	_map := map[string]int64{
		"BTC":          1000,
		"ETH":          1001,
		"TRX":          1002,
		"XRP":          1006,
		"BNB":          1007,
		"AVAX":         1009,
		"MATIC":        1010,
		"ETH_One":      1011,
		"ERC20":        1004,
		"TRC20":        1005,
		"BEP20":        1008,
		"USDT_C_Chain": 1012,
		"USDT_Polygon": 1013,
		"USDT_Onen":    1014,
		"USDC_ERC20":   1015,
		"USDC_TRC20":   1016,
		"USDC_BEP20":   1017,
		"USDC_C-Chain": 1018,
		"USDC_Polygon": 1019,
		"USDC_One":     1020,
		"SOL_Solana":   1021,
		"USDT_Solana":  1022,
		"USDC_Solana":  1023,
	}
	if _code, ok := _map[code]; ok {
		return _code
	}
	return 0
}

func (w *withdraw) WithdrawDetail(orderNo string) (resp WithdrawCallbackRequest, err error) {
	if orderNo == "" {
		err = errors.New("orderNo is empty")
		return
	}
	_data := map[string]interface{}{
		"orderNo": orderNo,
	}
	marshal, err := json.Marshal(_data)
	if err != nil {
		return
	}
	requestId := uuid.GenUUID().String()
	nonce := uuid.GenUUID().String()
	created := time.Now().Format(time.RFC3339)

	passwordDigest := GeneratePasswordDigest(nonce, created, w.AppSecret)

	sign := GenerateBodySign(string(marshal), w.AppCode, w.AppSecret, requestId)

	req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", w.Url, withdrawDetailPath), bytes.NewReader(marshal))
	if err != nil {
		return
	}
	// Set the appropriate headers
	req.Header.Set("Content-Type", "application/json; charset=UTF-8")
	req.Header.Set("Merchant-Id", w.MerchantId)
	req.Header.Set("Request-Id", requestId)
	req.Header.Set("Authorization", formatAuthorization(w.AppId))
	req.Header.Set("X-WSSE", formatXWSSE(w.AppCode, passwordDigest, nonce, created))
	req.Header.Set("Body-Sign", formatBodySing("HMAC-SHA256", sign))

	// Create a new HTTP client and send the request
	client := &http.Client{}
	response, err := client.Do(req)
	if err != nil {
		return
	}
	defer response.Body.Close()

	// Read the response body
	bodyResp, err := io.ReadAll(response.Body)
	if err != nil {
		return
	}
	_response := Response{}
	err = json.Unmarshal(bodyResp, &_response)
	if err != nil {
		return
	}
	if _response.Code != 0 {
		err = errors.New(_response.Msg)
		return
	}
	respMap := _response.Data.(map[string]interface{})
	resp = WithdrawCallbackRequest{
		OrderNo:       cast.ToString(respMap["orderNo"]),
		OuterNo:       cast.ToString(respMap["outerNo"]),
		AppId:         cast.ToInt64(respMap["appId"]),
		CurrencyId:    cast.ToInt64(respMap["currencyId"]),
		Amount:        cast.ToInt64(respMap["amount"]),
		Commission:    cast.ToInt64(respMap["commission"]),
		Scale:         cast.ToInt64(respMap["scale"]),
		Type:          cast.ToInt64(respMap["type"]),
		Status:        cast.ToInt64(respMap["status"]),
		IsTest:        cast.ToBool(respMap["isTest"]),
		CurrencyName:  cast.ToString(respMap["currencyName"]),
		CreateTime:    cast.ToInt64(respMap["createTime"]),
		ConfirmTime:   cast.ToInt64(respMap["confirmTime"]),
		RejectTime:    cast.ToInt64(respMap["rejectTime"]),
		RejectReason:  cast.ToString(respMap["rejectReason"]),
		Extend:        cast.ToString(respMap["extend"]),
		TransactionId: cast.ToString(respMap["transactionId"]),
	}
	resp.calAmount()
	return
}
